package com.neuronbit.businessflow.intellij.editor.swing.editor.propertyInspector.properties;

import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiField;
import com.intellij.psi.PsiPrimitiveType;
import com.intellij.psi.PsiType;
import com.intellij.psi.util.PsiTypesUtil;
import com.intellij.ui.JBColor;
import com.mxgraph.model.mxCell;
import com.mxgraph.model.mxGraphModel;
import com.mxgraph.model.mxICell;
import com.neuronbit.businessflow.intellij.dialog.FlowReturnFieldValueSourceTreeListChooseDialog;
import com.neuronbit.businessflow.intellij.editor.UIDesignerBundle;
import com.neuronbit.businessflow.intellij.editor.swing.editor.RadComponent;
import com.neuronbit.businessflow.intellij.editor.swing.editor.RadFlowComponent;
import com.neuronbit.businessflow.intellij.editor.swing.editor.RadJavaMethodComponent;
import com.neuronbit.businessflow.intellij.editor.swing.editor.propertyInspector.InplaceContext;
import com.neuronbit.businessflow.intellij.editor.swing.editor.propertyInspector.Property;
import com.neuronbit.businessflow.intellij.editor.swing.editor.propertyInspector.PropertyEditor;
import com.neuronbit.businessflow.intellij.editor.swing.editor.propertyInspector.PropertyRenderer;
import com.neuronbit.businessflow.intellij.editor.swing.editor.propertyInspector.renderers.LabelPropertyRenderer;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import static com.neuronbit.businessflow.intellij.util.PsiUtils.isAtomic;

/**
 * The type Field property.
 */
public class FlowReturnTypeProperty extends Property<RadJavaMethodComponent, String> {
    private final Project project;
    private final PropertyRenderer myRenderer;
    private final PropertyEditor myEditor = new MyPropertyEditor();
    private final String fieldName;
    private final @NotNull PsiType fieldType;

    /**
     * Instantiates a new Field property.
     *
     * @param parent    the parent
     * @param project   the project
     * @param fieldName the field name
     * @param fieldType the field type
     */
    public FlowReturnTypeProperty(@NotNull Property parent, Project project, @NotNull String fieldName, @NotNull PsiType fieldType) {
        super(parent, fieldName + ":" + fieldType.getPresentableText());
        this.project = project;
        this.myRenderer = new LabelPropertyRenderer<>();
        this.fieldName = fieldName;
        this.fieldType = fieldType;
    }

    @Override
    public @NotNull PropertyRenderer<String> getRenderer() {
        return myRenderer;
    }

    @Override
    public @Nullable PropertyEditor<String> getEditor() {
        return myEditor;
    }

    private String getFullFieldName() {
        Property parent = getParent();
        List<String> names = new ArrayList<>(3);
        while (parent != null) {
            String name = parent.getName();
            if (name.contains(":")) {
                names.add(StringUtils.strip(StringUtils.substringBefore(name, ":")));
            } else {
                names.add(StringUtils.strip(name));
            }
            parent = parent.getParent();
        }
        Collections.reverse(names);
        names.add(StringUtils.strip(fieldName));

        return String.join(".", names);
    }

    @Override
    public String getValue(RadJavaMethodComponent component) {
        return component.getReturnValue(getFullFieldName());
    }

    @Override
    protected void setValueImpl(RadJavaMethodComponent component, String value) throws Exception {
        component.setReturnValue(getFullFieldName(), value);
    }

    @Override
    public Property @NotNull [] getChildren(RadJavaMethodComponent component) {
//        return new Property[]{
//                new FieldNameProperty(this, component.getProject(), this.fieldName),
//                new FieldClassToBindProperty(this, component.getProject(), this.fieldType, "type"),
//                new FieldValueToBindProperty(this, component.getProject(), this.fieldName, this.fieldType, "value")
//        };

        if (StringUtils.isNotBlank(getValue(component)) || fieldType instanceof PsiPrimitiveType) {
            return EMPTY_ARRAY;
        }

        PsiClass returnPsiClass = ReadAction.compute(() -> PsiTypesUtil.getPsiClass(fieldType));

        if (null == returnPsiClass || isAtomic(returnPsiClass)) {
            return EMPTY_ARRAY;
        }

        List<PsiField> fields = Arrays.stream(ReadAction.compute(returnPsiClass::getAllFields))
                //.filter(field -> ReadAction.compute(() -> returnPsiClass.findMethodsByName("set" + StringUtils.capitalize(ReadAction.compute(field::getName)), true)).length != 0)
                .toList();

        if (fields.isEmpty()) {
            return EMPTY_ARRAY;
        }

        Property[] properties = new Property[fields.size()];
        for (int i = 0; i < fields.size(); i++) {
            PsiField field = fields.get(i);
            String fieldName = ReadAction.compute(field::getName);
            PsiType fieldType = ReadAction.compute(field::getType);

            properties[i] = new FlowReturnTypeFieldProperty(this, component.getProject(), fieldName, fieldType, fieldName);
        }
        return properties;
    }

    @Override
    public boolean needRefreshPropertyList() {
        return true;
    }

    private class MyPropertyEditor extends PropertyEditor {
        private final TextFieldWithBrowseButton myTf = new TextFieldWithBrowseButton();
        private RadComponent component;

        /**
         * Instantiates a new My property editor.
         */
        MyPropertyEditor() {
            //myTf.setText(UIDesignerBundle.message("client.properties.configure"));
            myTf.getTextField().setEditable(false);
            myTf.getTextField().setBorder(null);
            myTf.getTextField().setForeground(JBColor.foreground());
            myTf.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    showClientPropertiesDialog();
                }
            });
        }

        private void showClientPropertiesDialog() {
            FlowReturnFieldValueSourceTreeListChooseDialog dialog = new FlowReturnFieldValueSourceTreeListChooseDialog(project,
                    //TODO shunyun 2024/4/16: 这里的强转会导致复用的可能性降低，同时有可能有问题，因为泛型的类型与这里的类型不匹配
                    ((mxGraphModel) ((RadFlowComponent) component).getGraph().getModel())
                            .getCells()
                            .values()
                            .stream()
                            .filter(o -> o instanceof mxCell && ((mxCell) o).isVertex())
                            .map(mxICell.class::cast)
                            .toList(),
                    fieldName,
                    fieldType,
                    myTf.getText());
            if (dialog.showAndGet()) {
                List<String> result = dialog.getSelected();
                if (result != null) {
                    myTf.setText(String.join(",", result));
                }
            }
//            ConfigureClientPropertiesDialog dlg = new ConfigureClientPropertiesDialog(myProject);
//            dlg.show();
//            if (dlg.getExitCode() == DialogWrapper.OK_EXIT_CODE) {
//                dlg.save();
//                fireValueCommitted(true, false);
//            }
        }

        @Override
        public Object getValue() throws Exception {
            final String value = myTf.getText();
            return value.replace('$', '.'); // PSI works only with dots
        }

        @Override
        public JComponent getComponent(final RadComponent component, final Object value, final InplaceContext inplaceContext) {
            this.component = component;
            if (null != value) {
                myTf.setText(value.toString());
            } else {
                myTf.setText(UIDesignerBundle.message("client.properties.configure"));
            }
            return myTf;
        }

        @Override
        public void updateUI() {
            SwingUtilities.updateComponentTreeUI(myTf);
        }
    }
}
